package com.libnetwork

import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import com.libbase.Weak
import com.libbase.datarepository.DataRepository
import com.libnetwork.models.ServiceAddressModel
import com.libnetwork.websocketservice.WebSocketService
import io.reactivex.rxjava3.subjects.BehaviorSubject
import java.security.cert.Certificate

typealias RefreshTokenCompletion = (result: Boolean) -> Unit
typealias InvalidTokenCompletion = (result: Boolean) -> Unit

class Network private constructor() {
    interface Adapter {
        fun getToken(network: Network): String?

        fun getTokenType(network: Network): String?

        fun getAppID(network: Network): String?

        fun getHeaders(network: Network): Map<String, String>?

        fun refreshToken(completion: RefreshTokenCompletion?)

        fun invalidToken(message: String?, completion: InvalidTokenCompletion?)

        fun certificates(): List<Pair<String, List<Certificate>>>?
    }

    enum class NetworkReachabilityStatus {
        UNKNOWN,

        NOTREACHABLE,

        ETHERNETORWIFI,

        CELLULAR;

        val isReachable: Boolean
            get() {
                return this == ETHERNETORWIFI || this == CELLULAR
            }
    }

    companion object {
        val instance: Network by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Network()
        }
    }

    val reachabilityListener = BehaviorSubject.createDefault(NetworkReachabilityStatus.UNKNOWN)

    var adapter: Adapter? by Weak()

    val tokenType: String?
        get() = adapter?.getTokenType(this)

    val token: String?
        get() = adapter?.getToken(this)

    val appID: String?
        get() = adapter?.getAppID(this)

    var serviceAddressRepository: DataRepository<ServiceAddressModel>? = null

    fun initLib(context: Context) {
        WebSocketService.instance.initWebSocket()

        val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

        connMgr.activeNetwork?.let {
            connMgr.getNetworkCapabilities(it)?.let { networkCapabilities ->
                updateReachability(networkCapabilities)
            }
        } ?: run {
            reachabilityListener.onNext(NetworkReachabilityStatus.NOTREACHABLE)
        }

        connMgr.registerDefaultNetworkCallback(object : NetworkCallback() {
            override fun onAvailable(network: android.net.Network) {
                super.onAvailable(network)

                if (connMgr.activeNetwork == null || connMgr.activeNetwork == network) {
                    updateReachability(connMgr.getNetworkCapabilities(network))
                }
            }

            override fun onLost(network: android.net.Network) {
                super.onLost(network)

                if (connMgr.activeNetwork == null || connMgr.activeNetwork == network) {
                    updateReachability(connMgr.getNetworkCapabilities(network))
                }
            }

            override fun onCapabilitiesChanged(
                network: android.net.Network,
                networkCapabilities: NetworkCapabilities
            ) {
                super.onCapabilitiesChanged(network, networkCapabilities)

                updateReachability(networkCapabilities)
            }
        })
    }

    private fun updateReachability(networkCapabilities: NetworkCapabilities?) {
        networkCapabilities?.apply {
            reachabilityListener.onNext(
                if (hasCapability(NET_CAPABILITY_INTERNET) && hasCapability(NET_CAPABILITY_VALIDATED)) {
                    if (hasTransport(TRANSPORT_CELLULAR))
                        NetworkReachabilityStatus.CELLULAR
                    else
                        NetworkReachabilityStatus.ETHERNETORWIFI
                } else {
                    NetworkReachabilityStatus.NOTREACHABLE
                }
            )
        } ?: run {
            reachabilityListener.onNext(NetworkReachabilityStatus.NOTREACHABLE)
        }
    }
}